#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/select.h>
#include <signal.h>
#include <time.h>

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> /* Para el manejo de Threads */

#include "list.h"
#include "Estructuras.h"
#include "includes/Mensajes.h"
#include "includes/Threads.h"
#include "includes/InterfacePPD.h"

extern stRaidData raidDataGlobal; /*Datos generales del RAID, entre ellos un listado de todos los PPDs que tiene conectados */

int asignarThread(int iSocket)
{
	stThreadPPD data;
	pthread_t 	thread;
	int   estado;	/* Estado de salida para la espera de terminacion de los threads*/

	/* Agrego al listado de PPDs*/
	data.ID_PPD = 0;
	data.socketPPD = iSocket;
	data.QueueReads = collection_list_create();
	data.QueueWrites = collection_list_create();
	data.state = STATE_OPERATIVO;
	data.signal = crearSenial(SIGUSR2);
	collection_list_add(raidDataGlobal.listadoThreads, &data);


	if(pthread_create(&thread, NULL, atenderPPD, &data))
	{
		printf("ERROR; AL crear el thread\n");
		return EXIT_FAILURE;
	}

	data.TID_thread = thread;

	pthread_exit(NULL);
	/* no termino de entender poqrue el main tambien tiene q hacer pthread_exit pero bue
	lo saque de aca:
	https://computing.llnl.gov/tutorials/pthreads/
	*/
}

/* 	@NAME: atenderPPD()
	@DESC: funcion principal que ejecuta cada thread cuando se crea */
void* atenderPPD(stThreadPPD *thread)
{
	fd_set fdsSet; /* conj de descriptores donde escucha el select*/
	int maximo;
	int contRespuestas; /* Lo uso para saber cuando me llego la ultima rta de los pedidos para espejar */
	
	printf("************************************************\n");
	printf("\tThread ID: %ld\n\tAtiendo el PPD ID: %d - en el Socket %d\n",
			thread->TID_thread, thread->ID_PPD, thread->socketPPD);
	printf("************************************************\n");
	

    /* Select a la espera de se�ales y mensajes del PPD */
    FD_ZERO(&fdsSet);
    maximo = thread->socketPPD;

    FD_SET(thread->socketPPD, &fdsSet);
    FD_SET(thread->signal, &fdsSet);
    /* TODO esto es necesario? */

    if(thread->signal > thread->socketPPD)
    	maximo = thread->signal;

    select (maximo+1, &fdsSet, NULL, NULL, NULL);

    for(;;)
    {
    	/***** PPD ***********************/
    	if (FD_ISSET (thread->socketPPD, &fdsSet))
		{
			if(recepcionarPedido(thread) < 0)
			{
				FD_CLR(thread->socketPPD, &fdsSet);
				close(thread->socketPPD);
				/* Envio se�al al RAID para que replique mi cola */
				kill(raidDataGlobal.PID_RAID, raidDataGlobal.sfd_ReplicarDatos);
				/* Libero la queueWrite porque la QueueRead la voy a usar para la replica */
				collection_list_destroy(thread->QueueWrites, NULL);
				thread->state == STATE_NO_OPERATIVO;
				pthread_exit(NULL);
			}
			break;

		}

    	/***** SEÑAL *****************/
    	else if (FD_ISSET (thread->signal, &fdsSet))
		{
			if(thread->state == ESPEJANDO_MASTER)
				startEspejar(thread->socketPPD); /* envio pedidos al PPD para espejar */

			else if(thread->state == ESPEJANDO_SLAVE)
				processQueueMirror(thread->socketPPD); /* envio al PPD los pedidos de la QueueMirror */

			else if(thread->state == STATE_OPERATIVO)
			{
				processQueueRead(thread->QueueReads, thread->socketPPD);
				processQueueWrite(thread->QueueWrites, thread->socketPPD);
			}
		}
    }

	pthread_exit(NULL);
}

/* @NAME: recepcionarPedido
 * @DESC: recibe el mensaje del socket de comunicacion con el PPD,
 * lo identifica e invoca a la funcion correspondiente para atender cada caso.
 * */
int recepcionarPedido(stThreadPPD *thread)
{
	stMsgNIPC mensaje;
	int result;
	stPedido pedido;

	if(mensaje.Payload = malloc(sizeof(uint32_t)) == NULL) return EXIT_ERROR_MEMORY;
	result = recibirMensaje(thread->socketPPD, &mensaje);
	if(result == SOCKET_ERROR_DESCONEXION) return SOCKET_ERROR_DESCONEXION;
	if(result < 0) return SOCKET_ERROR_SOCKET;

	armarPedidoFromMensaje(&mensaje, &pedido);

	/******************************************************************
	 * Si Es una respuesta del PPD a un pedido que le hice para espejar
	 * ****************************************************************/
	if(thread->state == ESPEJANDO_MASTER)
	{
		/* nunca va a ser ESPEJANDO_SLAVE xq el SLAVE no envia pedidos al
			 * PPD hasta estar OPERATIVO*/
		if(pedido.socketFS == 0)
			atenderRespuestaParaEspejar(&thread, &pedido);
	}

	/* ******************************************************************
	 * Si Es una respuesta del PPD a un pedido de read/write que vino
	 * desde el File System
	 * *******************************************************************/
	if(pedido.socketFS != 0 || thread->state == STATE_OPERATIVO)
	{
		if(mensaje.PayloadDescriptor == MSG_GET_BOOT_SECTOR || mensaje.PayloadDescriptor == MSG_GET_SECTOR)
			procesarPedidoRead(thread, &pedido, &mensaje);

		else if(mensaje.PayloadDescriptor == MSG_WRITE_SECTOR)
			procesarPedidoWrite(thread, &pedido, &mensaje);
	}

}


/* @NAME: procesarPedidoRead
* @DESC: elimina de la queueWrite el pedido encolado
* y devuelve al FS la respuesta del PPD */

int procesarPedidoRead(stThreadPPD *thread, stPedido *pedido, stMsgNIPC *mensaje)
{
	int iResult;

	/* Eliminio de la QueueRead el nodo con mismo ID que mi pedido */
	int findIDPedido(void* data)
	{
		stPedido *nodo = (stPedido*)data;
		return(nodo->ID_Pedido == pedido->ID_Pedido);
	}
	/* TODO ver! El parametro que paso como NULL puede ser tambien el metodo encargado de liberar cada elemento de la lista.*/
	collection_list_removeByClosure(thread->QueueReads, findIDPedido, NULL);

	/* Envio la respuesta al fileSystem, su socket viaja en el pedido */
	if(mensaje->PayloadDescriptor == MSG_GET_BOOT_SECTOR)
		mensaje->PayloadDescriptor = MSG_RTA_GET_BOOT_SECTOR;
	else
		mensaje->PayloadDescriptor = MSG_RTA_GET_SECTOR;

	iResult = enviarMensaje(pedido->socketFS, mensaje); if(!iResult) return iResult;

	return EXIT_SUCCESS;
}


/* @NAME: procesarPedidoWrite
 * @DESC: elimina de la queueWrite el pedido encolado y devuelve al FS la respuesta del PPD */
int procesarPedidoWrite(stThreadPPD *thread, stPedido *pedido, stMsgNIPC *mensaje)
{
	int iResult;

	/* Eliminio de la QueueWrite el nodo con mismo ID que mi pedido */
	int findIDPedido(void* data)
	{
		stPedido *nodo = (stPedido*)data;
		return(nodo->ID_Pedido == pedido->ID_Pedido);
	}
	/* TODO ver! El parametro que paso como NULL puede ser tambien el metodo encargado de liberar cada elemento de la lista.*/
	collection_list_removeByClosure(thread->QueueWrites, findIDPedido, NULL);

	/* Envio la respuesta al fileSystem, su socket viaja en el pedido */
	mensaje->PayloadDescriptor = MSG_RTA_WRITE_SECTOR;

	iResult = enviarMensaje(pedido->socketFS, mensaje); if(!iResult) return iResult;

	return EXIT_SUCCESS;
}

/*
 * @NAME: atenderRespuestaParaEspejar
 * @DESC: graba en la QueueMirror (cola global deL RAID compartida por todos sus thread)
 * las respuestas del PPD a los pedido para espejar realizados en la funcion espejarDiscos()
 * */
int atenderRespuestaParaEspejar(stThreadPPD *thread, stPedido *pedido)
{
	/* Es una respuesta a un socket para espejar */
	extern int contRespuestas;


	/* Escribo el pedido en la QueueMirror */
	collection_list_add(raidDataGlobal.QueueMirror, pedido);
	contRespuestas++;

	/* Si esta es el ultima respuesta seteo a OPERATIVO y envio signal al RAID */
	if(contRespuestas == raidDataGlobal.totalSectores)
	{
		thread->state = STATE_OPERATIVO;
		kill(raidDataGlobal.PID_RAID, raidDataGlobal.sfd_EspejarDisco);
	}
}


/*
 * @NAME: espejarDisco
 * @DESC: inicia el proceso para espejar. Copia al nuevo thread todos los pedidos write
 * hechos hasta ahora y sincroniza con el thread maestro el pedido de lectura de todos sus
 * sectores. La recepcion de los mismos se realiza en otra funcion: atenderRespuestaParaEspejar
 * */
int espejarDisco()
{
   	pthread_t thread;
	stThreadPPD thread_Slave;
	stThreadPPD* thread_Master;
	stPedido* dataAux;
	int i, size = 0;
	
	printf("Por espejar Discos\n");

	
	/* Lo incorporo a mi lista de mis Threads */
	thread_Slave.ID_PPD = 0;
	thread_Slave.TID_thread = thread;
	thread_Slave.socketPPD = 0;
	thread_Slave.QueueReads = collection_list_create();
	thread_Slave.QueueWrites = collection_list_create();
	thread_Slave.state = ESPEJANDO_SLAVE;
	thread_Slave.signal = crearSenial(SIGUSR2);


	/* Lanzo un nuevo Thread = Thread_Slave */
	pthread_create(&thread, NULL, &atenderPPD, thread);

	
	collection_list_add(raidDataGlobal.listadoThreads, &thread_Slave);

	/* Busco un Thread cuyo state == OPERATIVO en mi lista de threads */
	
	/* Cuidad aca!!!! ver C-Talks: puntero a funciones: 
	https://docs.google.com/present/view?id=0AZ4-fywLgH9yZGZucDZuNWhfOTdjeDdudnNoaw&hl=en_US */
	
	int getOperatingThread(void *data) 
	{
		struct stThreadPPD *node = (struct stThreadPPD*) data;
		return(node->state == STATE_OPERATIVO);
	}
	thread_Master = (stThreadPPD*)collection_list_find(raidDataGlobal.listadoThreads, getOperatingThread);
	
	/* Seteo el state del maestro a espejando */
	thread_Master->state = ESPEJANDO_MASTER;

	
	/* y copio cada nodo a la QueueWrite del nuevo thread*/
	size = collection_list_size(thread_Master.QueueWrites);
	for(i = 0; i<size; i++)
	{
		dataAux = (stPedido*)collection_list_get(&(thread_Master->QueueWrites), i);
		dataAux->state = STATE_READY;
		collection_list_add( &(thread_Slave.QueueWrites), dataAux);
	}		

	/* Le envio Signal al Thread Maestro para que pida a su PPD �TODOS! los sectores */
	
	/* Uso la funcion kill(): 	The kill() function sends the signal specified by sig to pid, a process
    or a group of processes -> http://www.manpagez.com/man/2/kill/ 	*/
	
	kill(thread_Master->TID_thread, thread_Master->signal);
	pthread_exit(NULL);
	 
	return EXIT_SUCCESS;
}



